﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using SocketLib;
using System.Diagnostics;
using System.Linq;

struct User{
    public int ID;
    public string username;
    public string password;
    public string ip;
};
struct Group
{
    public string gName;
    public int IDChuToa;
    public List<User> DanhSach;
    public void GangName(string name)
    {
        this.gName = name;
    }
};

namespace ServerVideoCall
{
    public partial class frmServer : Form
    {
        #region Biến Xác Định Quyền Admin
        public int ROLE = 0;
        #endregion

        #region USER vs GROUP
        private List<User> ListUserOnline = new List<User>();
        // list Group
        private List<Group> ListGroup = new List<Group>();
        int countGroup = 0;

        string GroupRTP = "224.5.6.";
        #endregion

        #region FORM
        private UserConfiguration frmUser = new UserConfiguration();
        #endregion

        #region PORT
        private int port = 1450;
        private int portmuticast = 2000;
        private int portsendfile = 2500;
        #endregion

        #region SOCKET
        SocketLib.TCP.Server Server;
        #endregion

        #region Doi Tuong Truy Van
        DataProvider DuLieu;
        #endregion

        bool drag = false;
        Point PointCurrent;        
        // count sender
        private int count;
        private bool isStart = false;
        public frmServer()
        {
            InitializeComponent();
            DuLieu = new DataProvider();
            Server = new TCP.Server(port, 1024 * 5, 30, true);
            Server.RecieveData += new TCP.RecieveDataTcpServerEventHandler(Server_RecieveData);
            Server.ClientDisconnected += new TCP.ClientDisconnectedEventHandler(Server_ClientDisconnected);
            Server.Start();
        }

        #region 1 Client Ngắt kết nối
        void Server_ClientDisconnected(object sender, TCP.ClientDisconnectedEventArgs e)
        {
            var clientInfo = from p in ListUserOnline where p.ID == e.ClientInfo.ClientID select p;
            if (clientInfo.Count() != 0 )
                pBye(e.ClientInfo.ClientID);
        }
        #endregion

        #region Nhận Dữ Liệu Từ Client
        void Server_RecieveData(object sender, TCP.RecieveDataTcpServerEventArgs e)
        {
            try
            {
                Data msgReceived = new Data(e.Data);
                switch (msgReceived.cmdCommand)
                {
                    case Command.Connect:
                        {
                            pConnect(e);
                            break;
                        }
                    case Command.ChangePassword:
                        {
                            pChangePassword(e);
                            break;
                        }
                    case Command.Calling:
                        {
                            pCalling(e);
                            break;
                        }
                    case Command.AnswerCalling:
                        {
                            pAnswerCalling(e);
                            break;
                        }
                    case Command.CancelCalling:
                        {
                            pCancelCalling(e);
                            break;
                        }
                    case Command.JoinGroup:
                        {
                            pJoinGroup(e);
                            break;
                        }
                    case Command.EndCall:
                        {
                            pEndCall(e);
                            break;
                        }
                    case Command.AnswerJoin:
                        {
                            pAnswerJoin(e);
                            break;
                        }
                    case Command.Chat:
                        {
                            pChat(e);
                            break;
                        }
                    case Command.File:
                        {
                            pFile(e);
                            break;
                        }
                    case Command.Slider:
                        {
                            pSliderChange(e);
                            break;
                        }
                    case Command.AcceptFile:
                        {
                            pAcceptFile(e);
                            break;
                        }
                    case Command.No:
                        break;
                }
            }
            catch (Exception)
            {
            }
        }
        #endregion
        
        #region NHÓM HÀM XỬ Lý CHÍNH
        private void pConnect(TCP.RecieveDataTcpServerEventArgs e)
        {

            Data msgReceived = new Data(e.Data);
            string[] dl = msgReceived.stringdata.Split('*');
            if (DuLieu.CheckUsername(dl[0],dl[1],dl[2]) == true)
            {
                count++;
                // Add doi tuong user vua ket noi toi vao listuser
                User newus = new User();
                newus.ID = e.SenderInfo.ClientID;
                newus.username = dl[0];
                newus.password = dl[1];
                newus.ip = dl[2];
                ListUserOnline.Add(newus);

                // Gởi Thông báo kết nối thành công đến client
                SendToClient(Command.Connect, e.SenderInfo.ClientID, e.SenderInfo.ClientID.ToString());
                // Gởi Danh Sách user trong hệ thống cho tất cả các client
                SendToAllUser(Command.List, ListUserToString());
                // Gởi Danh Sách group trong hệ thống cho tất cả các client
                SendToAllUser(Command.Group, ListGroupToString());
                // Gởi portsendfile hien tai cho nó
                SendToAllUser(Command.File,portsendfile.ToString());
                Invoke(new MethodInvoker(delegate
                {
                    this.listBox1.Items.Add(dl[0] + " - LOG IN - (" + DateTime.Now + ") ");
                    listBox1.Refresh();
                    this.txtcount.Text = count.ToString();
                }));
            }
            else
            {
                SendCommand(Command.UserNameError, "sai username hoac password", e.SenderInfo.ClientID);                
            }
        }
        private string ListUserToString()
        {
            string str = "";
            int dem = 0;
            foreach (User us in ListUserOnline)
            {
                if (dem == 0)
                    str += us.username + "*" + us.ID;
                else
                    str += "," + us.username + "*" + us.ID;
                dem++;
            }
            return str;
        }
        private string ListGroupToString()
        {
            // GroupName2?1,2,3*GroupName2?4,5,6
            string EndList = "";
            int countgroup = 0;
            foreach (Group GP in ListGroup)
            {
                if (countgroup == 0)
                    EndList += GP.gName + "?";
                else
                    EndList += "*" + GP.gName + "?";
                int countclient = 0;
                foreach (User us in GP.DanhSach)
                {
                    if (countclient == 0)
                        EndList += us.username;
                    else
                        EndList += "," + us.username;
                    countclient++;
                }
                countgroup++;

            }
            return EndList;
        }
        private void SendToAllUser(Command cmd, string str)
        {
            foreach (User user in ListUserOnline)
                SendCommand(cmd, str, user.ID);
        }
        private void SendToClient(Command cmd, int ID, string message)
        {
            foreach (User user in ListUserOnline)
                if (user.ID == ID)
                    SendCommand(cmd, message, user.ID);
        }
        private void pBye(int ClientID)
        {
            string userout = "";
            foreach (User _us in ListUserOnline)
            {
                if (_us.ID == ClientID)
                {
                    userout = _us.username;
                    ListUserOnline.Remove(_us);
                    break;
                }
            }
            Invoke(new MethodInvoker(delegate
            {
                count--;
                this.listBox1.Items.Add(userout + " - LOG OUT - (" + DateTime.Now + ") ");
                listBox1.Refresh();
                this.txtcount.Text = count.ToString();
            }));
            CheckRemoveGroup(ClientID);
            SendToAllUser(Command.List, ListUserToString());
            if (ListUserOnline.Count == 0)
                portsendfile = 2500;
        }
        private void CheckRemoveGroup(int ClientID)
        {
           // Kiểm tra xem thằng đó có nằm trong group nào ko
            bool remove = false;
            int countgroup = 0;
            foreach (Group Gr in ListGroup)
            {
                if (Gr.IDChuToa == ClientID)
                {
                    // remove Group đó & send lệnh hủy cuoc gọi o group do toi tat ca cac thang con lai
                    foreach (User us in Gr.DanhSach)
                        SendCommand(Command.EndCall, Gr.gName, us.ID);
                    remove = true;
                    ListGroup.Remove(Gr);
                    break;
                }
                else
                {
                    foreach (User us in Gr.DanhSach)
                    {
                        if (us.ID == ClientID)
                        {
                            Gr.DanhSach.Remove(us);
                            SendToClient(Command.EndCall, ClientID, "EndCall");
                            break;
                        }
                    }
                    if (Gr.DanhSach.Count == 1)
                    {
                        foreach (User us in Gr.DanhSach)
                            SendCommand(Command.EndCall, Gr.gName, us.ID);
                        remove = true;
                        ListGroup.Remove(Gr);
                        break;
                    }
                }
                countgroup++;
            }
            if (remove == true)
            {
                List<Group> NewGroupList = new List<Group>();
                int dem = 0;
                foreach (Group Gr in ListGroup)
                {
                    if(dem >= countgroup && dem <= ListGroup.Count)
                    {
                        Group newGroup = new Group();
                        newGroup.gName = (dem + 1).ToString();
                        newGroup.IDChuToa = ListGroup[dem].IDChuToa;
                        foreach (User us in ListGroup[dem].DanhSach)
                        {
                            newGroup.DanhSach.Add(us);
                            SendCommand(Command.StartGroup, newGroup.gName.ToString(), us.ID);
                        }
                        NewGroupList.Add(newGroup);
                    }
                    dem++;
                }
            }
            SendToAllUser(Command.Group, ListGroupToString());
        }
        private void pChangePassword(TCP.RecieveDataTcpServerEventArgs e)
        {
            Data msg = new Data(e.Data);
            string[] temp = msg.stringdata.Split('*');
            DuLieu.ChangePasswordUser(temp[0],temp[1]);
            SendToClient(Command.Orther, e.SenderInfo.ClientID, "Bạn đã đổi mật khẩu thành công");

        }
        private void pCalling(TCP.RecieveDataTcpServerEventArgs e)
        {
            Data msg = new Data(e.Data);
            foreach (TCP.ClientInfo cl in Server.ClientInfoList)
                if (cl.ClientID.ToString() == msg.stringdata)
                    SendCommand(Command.Calling, e.SenderInfo.ClientID.ToString(), cl.ClientID);
        }
        private void pAnswerCalling(TCP.RecieveDataTcpServerEventArgs e)
        {
            Data msg = new Data(e.Data);
            string[] mg = msg.stringdata.Split('*');
            if (mg[0] == "YES")
            {
                // khởi tạo cuộc gọi mới gồm 2 thằng đó
                // Kiểm Tra Xem 2 thằng đó có nằm trong group nào ko, nếu có thì remove group đó ra.
                CheckRemoveGroup(e.SenderInfo.ClientID);
                CheckRemoveGroup(int.Parse(mg[1]));

                Group GP = new Group();
                GP.gName = (ListGroup.Count + 1).ToString();
                GP.DanhSach = new List<User>();
                GP.IDChuToa = int.Parse(mg[1]);

                foreach (User us in ListUserOnline)
                {
                    if (us.ID == GP.IDChuToa)
                        GP.DanhSach.Add(us);
                    if (us.ID == e.SenderInfo.ClientID)
                        GP.DanhSach.Add(us);
                }
                ListGroup.Add(GP);
                // send List Group Tới Tất Cả Các Client
                SendToAllUser(Command.Group, ListGroupToString());

                // Send phản hồi tới Người Gọi -  để tắt form show call
                SendToClient(Command.AnswerCalling, GP.IDChuToa,"Quit Form Show Call");

                //Send Lệnh Tạo RTPMuticast cho 2 thằng vừa tạo group
                SendToClient(Command.RTP, GP.IDChuToa,GroupRTP + GP.IDChuToa + "*" + portmuticast);
                SendToClient(Command.RTP, e.SenderInfo.ClientID, GroupRTP + GP.IDChuToa + "*" + portmuticast);

                // Send Lệnh StartGroup tới 2 thằng vừa tạo group
                SendToClient(Command.StartGroup, GP.IDChuToa, GP.gName);
                SendToClient(Command.StartGroup, e.SenderInfo.ClientID, GP.gName);

                // Send Xác Nhận Client là chủ tọa
                SendToClient(Command.MasterGroup, GP.IDChuToa, GP.gName);
            }
            else // NO
            {
                // Send phản hồi tới Người Gọi
                SendToClient(Command.AnswerCalling, int.Parse(mg[1]), "EndCall");
            }
        }
        private void pEndCall(TCP.RecieveDataTcpServerEventArgs e)
        {
            CheckRemoveGroup(e.SenderInfo.ClientID);
        }
        private void pCancelCalling(TCP.RecieveDataTcpServerEventArgs e)
        {
            Data msg = new Data(e.Data);
            foreach (User us in ListUserOnline)
            {
                if (us.username == msg.stringdata)
                {
                    SendCommand(Command.CancelCalling, "EndCalling", us.ID);
                    break;
                }
            }

        }
        private void pJoinGroup(TCP.RecieveDataTcpServerEventArgs e)
        {
            //temp[1]+"-"+txtusername.Text+"-"+YourID
            Data msg = new Data(e.Data);
            string[] user = msg.stringdata.Split('*');
            foreach (User us in ListUserOnline)
            {
                if (us.username == user[1])
                {
                    // send cho thằng chủ tọa hỏi có đồng ý hay không?
                    foreach (Group GP in ListGroup)
                    {
                        if (GP.gName == user[0])
                        {
                            SendCommand(Command.JoinGroup, user[1] + "*" + user[2] + "*" + GP.gName,GP.IDChuToa);
                            break;
                        }
                    }
                    break;
                }
            }
        }
        private void pAnswerJoin(TCP.RecieveDataTcpServerEventArgs e)
        {
            //YES/NO*IDChuToa*gName*IDThangMoi
            Data msg = new Data(e.Data);
            string[] ans = msg.stringdata.Split('*');
            {
                if (ans[0] == "YES")
                {
                    // Add nguoi muon vao group vao danh sach Client cua Group do
                    CheckRemoveGroup(int.Parse(ans[3]));
                    foreach (Group GP in ListGroup)
                    {
                        if (GP.gName == ans[2])
                        {
                            foreach (User us in ListUserOnline)
                            {
                                if (us.ID.ToString() == ans[3])
                                {
                                    GP.DanhSach.Add(us);
                                    break;
                                }
                            }
                        }
                    }
                    // gởi cập nhật danh sách group cho tat cac client
                    SendToAllUser(Command.Group, ListGroupToString());

                    // gởi lệnh tạo RTP Muticast cho thằng mới vào
                    SendToClient(Command.RTP,int.Parse(ans[3]),GroupRTP+ans[1]+"*"+ portmuticast);

                    // gởi lệnh startgroup cho thằng moi vao
                    SendToClient(Command.StartGroup, int.Parse(ans[3]), ans[2]); 
                }
                else // NO
                {
                    // gởi lệnh startgroup cho thằng moi vao
                    SendToClient(Command.AnswerJoin, int.Parse(ans[3]), "Chủ Tọa Không Đồng Ý Yêu Cầu Của Bạn");
                }
            }
        }
        private void pChat(TCP.RecieveDataTcpServerEventArgs e)
        {
            Data msg = new Data(e.Data);
            SendToAllUser(Command.Chat,msg.stringdata);
        }
        private void pSliderChange(TCP.RecieveDataTcpServerEventArgs e)
        {
            Data msg = new Data(e.Data);
            string []mes = msg.stringdata.Split('*');
            foreach (Group Gr in ListGroup)
            {
                if (Gr.gName == mes[0])
                {
                    foreach (User user in Gr.DanhSach)
                        SendCommand(Command.Slider, mes[1], user.ID);
                }
            }
        }
        private void pFile(TCP.RecieveDataTcpServerEventArgs e)
        {
            portsendfile++;
            SendToAllUser(Command.File, portsendfile.ToString());
        }
        private void pAcceptFile(TCP.RecieveDataTcpServerEventArgs e)
        {
            Data msg = new Data(e.Data);
            string[] temp = msg.stringdata.Split('*');
            // ip thang chap nhan*ID cua process* ID cua thẳng gởi
            SendToClient(Command.AcceptFile, int.Parse(temp[2]), temp[0] + "*" + temp[1]);
        }
        #endregion

        #region Cấu Trúc Gói Tin Truyền Nhận vs Lệnh Truyền
        private void SendCommand(Command cmd, string stringdata, int ClientID)
        {
            try
            {
                //Create the message to send.
                Data msgToSend = new Data();

                msgToSend.cmdCommand = cmd;
                msgToSend.stringdata = stringdata;

                byte[] message = msgToSend.ToByte();

                //Send the message asynchronously.
                Server.Send(ClientID, message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "VideoCall-SendMessage ()", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        enum Command
        {
            No,
            Connect,
            UserNameError,
            Calling,
            EndCall,
            AnswerCalling,
            MasterGroup,
            Slider,
            CancelCalling,
            StartGroup,
            List,
            Group,
            JoinGroup,
            AnswerJoin,
            ChangePassword,
            RTP,
            File,
            AcceptFile,
            Orther,
            Chat,
            Bye,
            Null
        }
        class Data
        {
            public Data()
            {
                this.cmdCommand = Command.Null;
                this.stringdata = "";
            }

            //Converts the bytes into an object of type Data.
            public Data(byte[] data)
            {
                //The first four bytes are for the Command.
                this.cmdCommand = (Command)BitConverter.ToInt32(data, 0);

                //The next four store the length of the name.
                int nameLen = BitConverter.ToInt32(data, 4);

                //This check makes sure that strName has been passed in the array of bytes.
                if (nameLen > 0)
                    this.stringdata = Encoding.UTF8.GetString(data, 8, nameLen);
                else
                    this.stringdata = null;
            }

            //Converts the Data structure into an array of bytes.
            public byte[] ToByte()
            {
                List<byte> result = new List<byte>();

                //First four are for the Command.
                result.AddRange(BitConverter.GetBytes((int)cmdCommand));

                //Add the length of the name.
                if (stringdata != null)
                    result.AddRange(BitConverter.GetBytes(stringdata.Length));
                else
                    result.AddRange(BitConverter.GetBytes(0));

                //Add the name.
                if (stringdata != null)
                    result.AddRange(Encoding.UTF8.GetBytes(stringdata));

                return result.ToArray();
            }

            public Command cmdCommand;
            public string stringdata;
        }
        #endregion

        private void btnexit_Click(object sender, EventArgs e)
        {
            if (isStart == true)
            {
                if (MessageBox.Show("Server Đang Chạy - Bạn Có Muốn Thoát?", "VideoCall - Closed", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    Send("Exit");
            }
            else
                Send("Exit");
        }
        private void frmServer_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && e.X > 0 && e.Y > 0 && e.X < 777 && e.Y < 50)
            {
                drag = true;
                PointCurrent = new Point(e.X, e.Y);
            }
            else
                drag = false;
        }
        private void frmServer_MouseUp(object sender, MouseEventArgs e)
        {
            drag = false;
        }
        private void frmServer_MouseMove(object sender, MouseEventArgs e)
        {
            if (drag == true)
            {
                Point NewPoint;
                NewPoint = this.PointToScreen(new Point(e.X,e.Y));
                NewPoint.Offset(-PointCurrent.X,-PointCurrent.Y);
                this.Location = NewPoint;
            }
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
        }
        void frmUser_ExitProGram()
        {
            frmUser.Close();
        }
        public delegate void ServerSend(string data);
        public event ServerSend Send = null;

        public virtual void OnNutDaNhan(string data)
        {
            if (Send != null)
                Send(data);
        }

        private void frmServer_Load(object sender, EventArgs e)
        {
            lbstatus.Text = "Server is starting...";
            lbstatus.Visible = true;
            clock1.UtcOffset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
        }

        private void btnuser_Click(object sender, EventArgs e)
        {
            frmUser.ROLE = this.ROLE;
            frmUser.ExitProGram += new UserConfiguration.Ext(frmUser_ExitProGram);
            frmUser.ShowDialog();
        }

        private void frmServer_FormClosing(object sender, FormClosingEventArgs e)
        {
            SendToAllUser(Command.Bye, "Mất kết nối với máy chủ");
            Process[] processes = Process.GetProcessesByName("Video Conference Server");
            foreach (Process process in processes)
                process.Kill();
        }
    }
}
